#include "voro_wrap.h"
#include "voropp/voro++.hh"
#include "buffer.h"
#include "vec3.h"
#include "volume_tester.h"

#define JC_VORONOI_IMPLEMENTATION
#include "jc_voronoi.h"
using namespace voro;

#define INIT_MEM 8

bool inset_faces(
    voronoicell_neighbor &c,
    const std::vector<Vec3> &verts,
    const std::vector<int> &faceFirstTriIxs,
    std::vector<double> &nor,
    std::vector<int> &neigh,
    double inset);

double *calculate_voronoi(double *data, double distance)
{
    // Output will go here
    Buffer buf;
    buf.push(0); // total number of doubles in buffer
    buf.push(0); // number of cells

    jcv_rect rect;
    rect.min.x = rect.min.y = JCV_FLT_MAX;
    rect.max.x = rect.max.y = -JCV_FLT_MAX;

    size_t pos = 0;
    size_t nPoints = (size_t)data[pos++];

    jcv_point* points = (jcv_point*)malloc( sizeof(jcv_point) * nPoints );
    for (size_t i = 0; i < nPoints; ++i)
    {
        points[i].id = (int)data[pos++];
        points[i].x = (float)data[pos++];
        points[i].y = (float)data[pos++];
        jcv_rect_union(&rect, &points[i]);
    }

    jcv_diagram diagram;
    const jcv_site* sites;
    jcv_graphedge* graph_edge;

    memset(&diagram, 0, sizeof(jcv_diagram));

    jcv_diagram_generate(nPoints, points, &rect, 0, &diagram, distance);
    sites = jcv_diagram_get_sites(&diagram);

    size_t nParticles = 0;
    for (size_t i = 0; i < nPoints; i++)
    {
        const jcv_site* site = &sites[i];
        jcv_point s = site->p;
        ++nParticles;
        buf.push((double)site->index);
        buf.push((double)s.x);
        buf.push((double)s.y);
        buf.push((double)site->count);
        buf.push((double)site->parent);
    }
    jcv_diagram_free(&diagram);
    free(points);
    buf.set_at(1, (double)nParticles);
    buf.set_at(0, (double)buf.pos);
    return (double*)buf.buf;
}

bool inset_faces(
    voronoicell_neighbor &c,
    const std::vector<Vec3> &verts,
    const std::vector<int> &faceFirstTriIxs,
    std::vector<double> &nor,
    std::vector<int> &neigh,
    double inset)
{
    bool cellRemaining = true;

    // Get cell's normal vectors
    c.normals(nor);
    c.neighbors(neigh);

    // Plane with each of them
    size_t nFaces = c.number_of_faces();
    for (size_t i = 0; cellRemaining && i < nFaces; ++i)
    {
        // Face's neighbor is a wall: not insetting this face
        if (neigh[i] < 0) continue;

        // Face's normal vector; particle
        Vec3 n(nor[i*3], nor[i*3+1], nor[i*3+2]);

        // Distance of face's plane from particle
        Vec3 v1 = verts[faceFirstTriIxs[i*3]];
        Vec3 v2 = verts[faceFirstTriIxs[i*3+1]];
        Vec3 v3 = verts[faceFirstTriIxs[i*3+2]];
        double d = distPlaneOrigin(v1, v2, v3);

        // Shave off the cell
        double len = (d - inset) * 2;
        if (len >= 0) cellRemaining &= c.plane(n.x, n.y, n.z, len);
        else
        {
            n.mul(-1);
            cellRemaining &= c.plane(n.x, n.y, n.z, -len);
        }
    }

    return cellRemaining;
}

void *create_volume_tester(double *data)
{
    return new VolumeTester(data);
}

void delete_volume_tester(void *ptr)
{
    VolumeTester *vt = (VolumeTester*)ptr;
    delete vt;
}

bool is_point_inside(void *ptr, double x, double y, double z)
{
    VolumeTester *vt = (VolumeTester*)ptr;
    return vt->IsPointInside(x, y, z);
}


